home *** CD-ROM | disk | FTP | other *** search
- /*
- * File: mp_intp.c
- * SGoldthorpe 20-Jul-91
- */
-
- /*
- * mp_intp - the midi file interpreter for midiplay
- * This software is (C) 1991 Stephen Goldthorpe but it's FREE! Usual
- * disclaimers and notices about this software not being sold for profit.
- * But you may take all you want from the code though! If you have any
- * suggestions/bug fixes please get in contact with me. I don't want to
- * maintain code i've never even seen before (life's hard enough without all
- * of that)!
- * -Steve Goldthorpe
- * Phone (DAYTIME UK): +44 707 382350
- * Internet E-Mail: SGoldthorpe.wgc-e@rx.xerox.com
- * goldthor@arisia.xerox.com
- */
-
- #include <stdio.h>
- #include <types.h>
- #include <time.h>
- #include <string.h>
-
- /* and for the atari OS stuff */
- #include <osbind.h>
-
- /* #include "midiplay.h" included by mp_gbls.h */
- #include "mp_gbls.h"
-
- /* GLOBAL VARIABLES */
- static double track_delta[MAX_TRACKS], division, time_per_beat;
- static BYTE *track_pos[MAX_TRACKS];
- static int track_left[MAX_TRACKS];
- static int track_finished[MAX_TRACKS];
- static clock_t clock_orig;
- static char *gFile;
- static WORD format, tracks;
- static int finished_tracks;
- static int i;
-
- /* FUNCTION DECLS */
- BOOL interp();
- static void truncated(), all_notes_off(), parse_error();
-
- /* MACRO FUNCTIONS */
- /* the error checking may be a bit OTT but I'm gonna do it anyway (helps
- catch those naughty bugs - and bad files) */
- #define GET32BITS(dw,p,l) dw=(((LONG)(*p)<<24)+ \
- ((LONG)(*(p+1))<<16)+ \
- (((LONG)*(p+2))<<8)+ \
- (LONG)(*(p+3))); \
- if(l<4) \
- { truncated(); \
- return(FALSE); \
- }; \
- p += 4; l -= 4
-
- #define GET16BITS(w,p,l) w=(((WORD)(*p)<<8)+(WORD)*(p+1)); \
- if(l<2) \
- { truncated(); \
- return(FALSE); \
- } \
- p += 2; l -= 2
-
- #define GET8BITS(b,p,l) b = *(p)++; \
- if(--l<0) \
- { truncated(); \
- return(FALSE); \
- }
-
- #define GETVARLEN(dw,p,l) for(dw=(LONG)(*p)&0x7f;(*(p)++)&0x80;) \
- { if(--l<0) \
- { truncated(); \
- return(FALSE); \
- }; \
- dw <<=7; \
- dw |= (LONG)(*p)&0x7f; \
- }; \
- if(--l<0) \
- { truncated(); \
- return(FALSE); \
- }
-
- #define CHECKLEFT(l,v) if(l<v) \
- { truncated(); \
- return(FALSE); \
- }
-
- #define SEND(b) Bconout(3,b)
-
- /* FUNCTION DEFS */
- BOOL interp(buffer, file, len)
- char *file;
- BYTE *buffer;
- unsigned int len;
- { BYTE *pos=buffer,*next;
- BYTE running_status[MAX_TRACKS],last_running_status;
- WORD w;
- LONG dw,delta;
- double clock_delta;
- unsigned int left=len;
-
- gFile=file;
-
- /* check header */
- if((left<4)||(strncmp("MThd",(char*)pos,4)!=0))
- { (void)fprintf(stderr,"%s: %s is not a midi file\n",app_name,file);
- return(FALSE);
- };
- pos += 4; left -= 4;
-
- /* find address of next chunk */
- GET32BITS(dw,pos,left);
- next=pos+dw;
- #ifdef DEBUG
- (void)printf("pos is %lx len is %ld next would be %lx\n",pos-buffer,dw,
- next-buffer);
- #endif
-
- /* get file format */
- GET16BITS(format,pos,left);
- switch (format)
- { /* OK we accept formats 0 and 1 */
- case 0:
- case 1:
- break;
- /* but we don't do any others */
- default:
- (void)fprintf(stderr,"%s: can't play %s, midi file type %d\n",app_name,
- file,format);
- return(FALSE);
- };
-
- /* get number of tracks */
- GET16BITS(tracks,pos,left);
-
- /* check tracks in range */
- if(tracks > MAX_TRACKS)
- { (void)fprintf(stderr,"%s: %s has too many tracks (%d allowed).\n",
- app_name,file,tracks,MAX_TRACKS);
- return(FALSE);
- };
-
- /* get division - this is the division of a quarter note or if negative is
- frame based. */
- GET16BITS(w,pos,left);
- division=(double)w;
-
- /* I don't suport the frame stuff yet! */
- if(division < 0)
- { (void)fprintf(stderr,
- "%s: file %s - don't support framed based files yet!", app_name,
- file);
- return(FALSE);
- };
- #ifdef DEBUG
- (void)printf("division = %f\n",division);
- #endif
-
- time_per_beat = (double)(CLK_TCK * 60.0)/120.0; /* default 120bpm */
- #ifdef DEBUG
- (void)printf("time per beat (1/200 sec) %f\n",time_per_beat);
- #endif
-
- /* do some initialisation, track finding etc */
- finished_tracks = 0;
- for(i=0;i<tracks;i++)
- { CHECKLEFT(left,(next-pos));
- left -= next-pos; pos = next;
-
- /* track start */
- if((left<4)||(strncmp("MTrk",(char*)pos,4)!=0))
- { (void)fprintf(stderr,"%s: %s parse error, track expected\n",
- app_name,file);
- #ifdef DEBUG
- (void)printf("posn %lx, bytes around (-3..3) %02x %02x %02x %02x \
- %02x %02x %02x\n",pos-buffer,*(pos-3),*(pos-2),*(pos-1),*pos,*(pos+1),*(pos+2),
- *(pos+3));
- #endif
- return(FALSE);
- };
- pos += 4; left -= 4;
-
- /* find address of next chunk */
- GET32BITS(dw,pos,left);
- *(track_pos+i) = pos;
- next = pos+dw;
- *(track_left+i) = (int)dw;
- #ifdef DEBUG
- (void)printf("pos is %lx len is %ld next would be %lx\n",pos-buffer,dw,
- next-buffer);
- #endif
-
- /* get initial delta time */
- GETVARLEN(dw,*(track_pos+i),*(track_left+i));
- *(track_delta+i)=(double)dw;
- *(track_finished+i)=FALSE;
- *(running_status+i) = 0xfe;
- last_running_status = 0xfe;
- };
-
- /* let the user know what's happening */
- (void)printf("playing '%s' with %d tracks\n",file,tracks);
-
- /* get start time */
- clock_orig=clock();
-
- /* dispatcher */
- while (finished_tracks != tracks)
- { BYTE event;
- clock_delta = (double)(clock()-clock_orig)*division/time_per_beat;
- for(i=0;i<tracks;i++)
- { if(!*(track_finished+i) & (clock_delta>*(track_delta+i)))
- { GET8BITS(event,*(track_pos+i),*(track_left+i));
-
- /* parse event */
- switch (event)
- { /* meta-events */
- case 0xff:
- { if(!meta_event())
- return(FALSE);
- break;
- };
- /* sysex events */
- case 0xf0:
- case 0xf7:
- { if(!sysex_event(event))
- return(FALSE);
- break;
- };
- case 0xf1:
- case 0xf2:
- case 0xf3:
- case 0xf4:
- case 0xf5:
- case 0xf6:
- { if(!system_common(event))
- return(FALSE);
- break;
- };
- case 0xf8:
- case 0xf9:
- case 0xfa:
- case 0xfb:
- case 0xfc:
- case 0xfd:
- case 0xfe:
- { if(!system_real_time())
- return(FALSE);
- break;
- };
- /* midi events */
- default:
- { switch(event & 0xf0)
- { /* 3 byte events */
- case 0x90:
- case 0x80:
- case 0xa0:
- case 0xb0:
- case 0xe0:
- { BYTE c;
- SEND(event);
- GET8BITS(c,*(track_pos+i),*(track_left+i));
- SEND(c);
- GET8BITS(c,*(track_pos+i),*(track_left+i));
- SEND(c);
- *(running_status+i) = event;
- last_running_status = event;
- break;
- };
- /* program change */
- case 0xc0:
- { BYTE c;
- GET8BITS(c,*(track_pos+i),*(track_left+i));
- if (f_Program)
- { SEND(event);
- SEND(c);
- *(running_status+i) = event;
- last_running_status = event;
- };
- break;
- };
- /* channel pressure */
- case 0xd0:
- { BYTE c;
- GET8BITS(c,*(track_pos+i),*(track_left+i));
- if (f_Channel_pressure)
- { SEND(event);
- SEND(c);
- *(running_status+i) = event;
- last_running_status = event;
- };
- break;
- };
- default:
- { /* running status */
- if(*(running_status+i)!=last_running_status)
- { SEND(*(running_status+i));
- last_running_status=*(running_status+i);
- };
- if((event&0x80)==0)
- { SEND(event);
- #ifdef DEBUG
- (void)printf("running stat (%02x) %02x\n",
- *(running_status+i),event);
- #endif
- switch(*(running_status+i) & 0xf0)
- { /* 3 byte events */
- case 0x90:
- case 0x80:
- case 0xa0:
- case 0xb0:
- case 0xe0:
- { BYTE c;
- GET8BITS(c,*(track_pos+i),
- *(track_left+i));
- SEND(c);
- break;
- };
- /* ignoring other 0xf? events - naughty
- aren't I */
- };
- };
- };
- };
- };
- };
-
- /* get delta time - but only if we're still going on this
- track */
- if(!*(track_finished+i))
- { GETVARLEN(delta,*(track_pos+i),*(track_left+i));
- *(track_delta+i) += (double)delta;
- };
- };
- };
- /* CHECK TO SEE IF CTRL C/S PRESSED */
- if(Bconstat(2))
- { BYTE c;
- c=(Bconin(2)&0xff);
- switch(c)
- { case 3: /* CTRL C */
- { printf("\n** Exit by CTRL C **\n");
- all_notes_off();
- return(TRUE);
- };
- case 19: /* CTRL S */
- { printf("\n** Skip track by CTRL S **\n");
- all_notes_off();
- return(FALSE);
- };
- };
- };
- };
- return(FALSE);
- };
-
- /* system exclusive events */
- int sysex_event(event)
- BYTE event;
- { LONG length,l;
- GETVARLEN(length,*(track_pos+i),*(track_left+i));
- if(f_Sysex)
- { BYTE c;
- if(event==0xf0)
- SEND(0xf0);
- for(l=0;l<length;l++)
- { GET8BITS(c,*(track_pos+i),*(track_left+i));
- SEND(c);
- };
- }
- else
- { CHECKLEFT(*(track_left+i),(int)length);
- *(track_pos+i) += length;
- *(track_left+i) -= length;
- };
- return(TRUE);
- };
-
- /* meta-events */
- int meta_event()
- { BYTE type;
- LONG length;
-
- /* get type of meta event */
- GET8BITS(type,*(track_pos+i),*(track_left+i));
- #ifdef DEBUG
- (void)printf("meta-event %02x ",type);
- #endif
-
- /* get length */
- GETVARLEN(length,*(track_pos+i),*(track_left+i));
- #ifdef DEBUG
- (void)printf("length %ld\n",length);
- #endif
-
- switch(type)
- { /* textual meta-events */
- case 0x01:
- case 0x02:
- case 0x03:
- case 0x04:
- case 0x05:
- case 0x06:
- case 0x07:
- { int flag;
- char *msg;
- switch(type)
- { case 0x01:
- flag=f_text;
- msg="Text: \"";
- break;
- case 0x02:
- flag=f_copyright;
- msg="Copyright: \"";
- break;
- case 0x03:
- flag=f_track_name;
- msg="Track\Sequence: \"";
- break;
- case 0x04:
- flag=f_instrument;
- msg="Instrument: \"";
- break;
- case 0x05:
- flag=f_lyric;
- break;
- case 0x06:
- flag=f_marker;
- msg="Marker: \"";
- break;
- case 0x07:
- flag=f_prompt;
- msg="Prompt: \"";
- break;
- };
- if (flag)
- { LONG l;
- BYTE c;
- /* lyrics should have no header */
- if(type!=0x05)
- printf(msg);
- for(l=0;l<length;l++)
- { GET8BITS(c,*(track_pos+i),*(track_left+i));
- putchar(c);
- };
- /* lyrics should be on same line */
- if(type!=0x05)
- printf("\"[Tr %d]\n",i);
- }
- else
- { CHECKLEFT(*(track_left+i),(int)length);
- *(track_pos+i) += length;
- *(track_left+i) -= length;
- };
- break;
- };
- /* u-sec tempo set */
- case 0x51:
- { LONG t;
- BYTE c;
- GET8BITS(c,*(track_pos+i),*(track_left+i));
- t=(LONG)c<<16;
- GET8BITS(c,*(track_pos+i),*(track_left+i));
- t += (LONG)c<<8;
- GET8BITS(c,*(track_pos+i),*(track_left+i));
- t += (LONG)c;
- #ifdef DEBUG
- (void)printf("usec %ld ",t);
- #endif
- t=(t*(LONG)CLK_TCK)/1000000L;
- time_per_beat = (double)t;
- #ifdef DEBUG
- (void)printf("our %f\n",time_per_beat);
- #endif
- break;
- };
-
- /* end of track - can't be bothered to check left == 0 too */
- case 0x2f:
- *(track_finished+i)=TRUE;
- finished_tracks++;
- break;
-
- /* ignore rest */
- default:
- CHECKLEFT(*(track_left+i),(int)length);
- *(track_pos+i) += length;
- *(track_left+i) -= length;
- break;
- };
- return(TRUE);
- };
-
- static int system_common(event)
- BYTE event;
- { switch(event)
- { /* 3 byte instructions */
- case 0xf2:
- { CHECKLEFT(*(track_left+i),2);
- *(track_pos+i) += 2;
- *(track_left+i) -= 2;
- break;
- };
- /* 2 byte instructions */
- case 0xf1:
- case 0xf3:
- { CHECKLEFT(*(track_left+i),1);
- *(track_pos+i) += 1;
- *(track_left+i) -= 1;
- break;
- };
- /* rest are single byte */
- };
- return(TRUE);
- };
-
- static int system_real_time()
- { /* all 1 byte so ignore */
- return(TRUE);
- };
-
- static void all_notes_off()
- { /* MANUALLY TURN ALL NOTES OFF */
- /* time= (2+8)x16x(1+128x2)/31250 ~= 1.5 seconds */
- int i,j;
- for(i=0;i<16;i++)
- { SEND((BYTE)(0x90+i));
- for(j=0;j<128;j++)
- { SEND((BYTE)j);
- SEND(0);
- };
- };
- };
-
- /* general error messages - I got fed up typing these over and over again */
- static void truncated()
- { (void)fprintf(stderr,"%s: %s truncated\n",app_name,gFile);
- };
-
- /*
- * REVISION LOG
- * ============
- * 0.1 SGoldthorpe 20-Mar-91 Created for Atari ST / Sozobon C. It's
- * a bit atari specific in places but i've
- * tried to make it UNIX(tm) looking for
- * easier porting (if anyone feels brave
- * enough to try.
- * 0.2 SGoldthorpe 7-Apr-91 Messed up the code in mp_intp to
- * allow type 1 midi files. Timing is
- * still a bit hairy but it plays 80%
- * of the files I have OK.
- * 0.3 SGoldthorpe 27-May-91 Reformatted & tidied up, sorted out
- * running status and added flags.
- * 0.4 SGoldthorpe 20-Jul-91 Generally restructed and tidied up and
- * added sysex events.
- *
- */
-